package joc.internal.transformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.annotation.NoSuchClassError;
import joc.customize.Contract;
import joc.customize.ForParameterEvaluateAtBeginningOfBehavior;

public class ParameterPreConditionBehaviorTransformer extends DeclaredBehaviorTransformer {

	public ParameterPreConditionBehaviorTransformer(ClassPool pool) {
		super(pool);
	}

	@Override
	public void transform(CtClass clazz, CtBehavior behavior) throws Exception {
		logger.info("behavior " + behavior.getLongName());
		for (int argNum = 1; argNum <= behavior.getParameterTypes().length; argNum++) {
			logger.info(" parameter " + argNum);
			for (Class<? extends Annotation> contractAnnotation : Transformer.getContractMap().keySet()) {
				Annotation annotation = getParameterAnnotation(behavior, argNum, contractAnnotation);
				if (annotation != null) {
					logger.info("method " + behavior.getLongName() + " parameter " + argNum + " has contract "
							+ Transformer.getContractMap().get(contractAnnotation).getClass().getName());
					applyContractForParameter(behavior, Transformer.getContractMap().get(contractAnnotation), argNum,
							annotation);
				}
			}
		}
	}

	private void applyContractForParameter(CtBehavior behavior, Contract contract, int argNum, Annotation annotation)
			throws Exception {
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForParameterEvaluateAtBeginningOfBehavior.class)) {
			behavior.insertBefore(getContractCallString(contractMethod, contract, annotation, true, "("
					+ contractMethod.getParameterTypes()[1].getName() + ") $" + argNum));
			logger.info("inserted " + contract.getClass().getName() + " in method " + behavior.getLongName()
					+ " at parameter " + argNum);
		}
	}

	private Annotation getParameterAnnotation(CtBehavior behavior, int argNum,
			Class<? extends Annotation> annotationClass) throws ClassNotFoundException, NoSuchClassError {
		MethodInfo mi = behavior.getMethodInfo2();
		ParameterAnnotationsAttribute ainfo = (ParameterAnnotationsAttribute) mi
				.getAttribute(ParameterAnnotationsAttribute.invisibleTag);
		ParameterAnnotationsAttribute ainfo2 = (ParameterAnnotationsAttribute) mi
				.getAttribute(ParameterAnnotationsAttribute.visibleTag);
		Annotation result = parameterAnnotationsIncludeClass(argNum, annotationClass, ainfo);
		if (result != null) {
			return result;
		}
		return parameterAnnotationsIncludeClass(argNum, annotationClass, ainfo2);
	}

	private Annotation parameterAnnotationsIncludeClass(int argNum, Class<?> annotationClass,
			ParameterAnnotationsAttribute ainfo) throws ClassNotFoundException, NoSuchClassError {
		if (ainfo != null && ainfo.getAnnotations()[argNum - 1] != null) {
			for (javassist.bytecode.annotation.Annotation annotation : ainfo.getAnnotations()[argNum - 1]) {
				if (annotation.getTypeName().equals(annotationClass.getName())) {
					return (Annotation) annotation.toAnnotationType(pool.getClassLoader(), pool);
				}
			}
		}
		return null;
	}

}
